﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Net;
using System.Threading;
using QuickTuner.Util;

namespace QuickTuner.Util
{
    // TODO: Generalizes the tempfile/weakreference approach into a
    //       WeakReferenceDictionary
    // TODO: Decouples the class from Station's implementation

    /// <summary>
    /// Download icon for stations and maintain the memory/disk cache.
    /// </summary>
    public static class IconCache
    {
        public const int DownloadBufferSize = 256 * 1024;        // 256KB buffer

        private static Func<string, Image> getIcon = GetIcon;

        private static Dictionary<string, WeakReference> _iconCache;
        private static Mutex _iconCacheMutex;
        private static Dictionary<string, string> _tempFiles;
        private static Mutex _tempFilesMutex;

        // constructors _______________________________________________________

        static IconCache()
        {
            _tempFilesMutex = new Mutex(false);
            _tempFiles = new Dictionary<string, string>();

            _iconCacheMutex = new Mutex(false);
            _iconCache = new Dictionary<string, WeakReference>();
        }

        // methods ____________________________________________________________

        public static Image GetIcon(Station s)
        {
            return GetIcon(s.StationURL);
        }

        public static IAsyncResult BeginGetIcon(Station s, AsyncCallback callback)
        {
            return getIcon.BeginInvoke(s.StationURL, callback, null);
        }

        public static IAsyncResult BeginGetIcon(Station s, AsyncCallback callback, object state)
        {
            return getIcon.BeginInvoke(s.StationURL, callback, state);
        }

        public static Image EndGetIcon(IAsyncResult ar)
        {
            return getIcon.EndInvoke(ar);
        }

        // actual download routine, invoked in its own separate thread
        private static Image GetIcon(string url)
        {
            var ms = new MemoryStream();
            Image img = null;

            // try finding the favicon's url first
            var iconUrl = GetIconUrl(url);
            if (string.IsNullOrEmpty(iconUrl)) return null;

            // check if we still have icon in either the weak reference cache
            // or saved in temp files, trying loading from those sources
            img = tryGetIconFromCache(iconUrl)
                ?? tryGetIconFromTempFile(iconUrl);

            if (img != null) return img;

            // no icon in the cache, downloads live
            var wr = WebRequest.CreateDefault(new Uri(iconUrl));
            WebResponse response;
            Stream stream = null;

            try {
                response = wr.GetResponse();
                stream = response.GetResponseStream();

                // download and save in memory first deferring Image data parsing
                // in case of web/network error
                var bytesRead = 0;
                var buffer = new byte[DownloadBufferSize];

                while ((bytesRead = stream.Read(buffer, 0, DownloadBufferSize)) > 0) {
                    ms.Write(buffer, 0, bytesRead);
                }

                // release the buffer immediatly
                buffer = null;

            } catch (WebException) {

                // image not found or unable to parse image
                return null;

            } finally {

                if (stream != null) {
                    stream.Close();
                    stream.Dispose();
                }

            }

            // try parsing only if we have some data to parse
            if (ms.Length > 0) {
                try {
                    // try parsing as Icon first
                    ms.Seek(0, SeekOrigin.Begin);
                    img = new Icon(ms).ToBitmap();

                } catch {
                    try {
                        // if fail, try GDI+ generic Image parser
                        ms.Seek(0, SeekOrigin.Begin);
                        img = Image.FromStream(ms);

                    } catch {
                        // fail
                        img = null;
                    }
                }
            }

            // if parsing successful, save them in the cache
            if (img != null) {
                saveToCache(iconUrl, img);
                saveToTempFile(iconUrl, img);

            }

            ms.Close();
            ms.Dispose();

            return img;
        }

        public static string GetIconUrl(string stationUrl)
        {
            try {
                var uri = new Uri(stationUrl);
                return "http://{0}/favicon.ico".F(uri.Host);

            } catch {
                return null;

            }
        }

        // cache helpers

        private static void saveToCache(string url, Image icon)
        {
            _iconCacheMutex.WaitOne();
            _iconCache[url] = new WeakReference(icon, false);
            _iconCacheMutex.ReleaseMutex();
        }

        private static Image tryGetIconFromCache(string url)
        {
            Image img = null;

            _iconCacheMutex.WaitOne();

            // does the cache have the object
            if (_iconCache.ContainsKey(url)) {

                // fetch from the weak reference cache
                img = _iconCache[url].Target as Image;

                // is the object still alive? (since it's a weak reference)
                // if not we also remove the key from the cache
                if (img == null)
                    _iconCache.Remove(url);

            }

            _iconCacheMutex.ReleaseMutex();

            return img;
        }

        private static void saveToTempFile(string url, Image icon)
        {
            // generate a temp filename with the same extension
            var tempPath = Path.GetTempFileName();
            tempPath = Path.ChangeExtension(tempPath, Path.GetExtension(url));

            // save the temp filename to cache
            _tempFilesMutex.WaitOne();
            _tempFiles[url] = tempPath;
            _tempFilesMutex.ReleaseMutex();

            // and to disk
            icon.Save(tempPath);
        }

        private static Image tryGetIconFromTempFile(string url)
        {
            Image img = null; ;

            _tempFilesMutex.WaitOne();

            // have we saved it to temp files before?
            if (_tempFiles.ContainsKey(url)) {

                // gets the temp filename
                var tempFilename = _tempFiles[url];

                // file still exists? (since it's a temporary file)
                if (File.Exists(tempFilename)) {

                    // file still exists, we load form the file
                    try { img = Image.FromFile(tempFilename); } catch { img = null; }

                } else
                    // remove from cache if temp file no longer exists
                    _tempFiles.Remove(url);

            }

            _tempFilesMutex.ReleaseMutex();

            return img;
        }

    }
}
